/*
 *
 * Copyright (c) 2023 - future
 * @author fzh
 * @email e_341877120_mail@163.com
 * @link https://mvnrepository.com/search?q=io.github.xrfzh.cn
 *
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package cn.xrfzh.biz.user.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import cn.xrfzh.api.user.dto.*;
import cn.xrfzh.api.user.vo.LoginVo;
import cn.xrfzh.biz.video.mapper.VideoMapper;
import cn.xrfzh.common.constants.AESAlgorithmConstant;
import cn.xrfzh.common.constants.DefaultUserInfoConstant;
import cn.xrfzh.common.constants.PhoneLengthConstant;
import cn.xrfzh.common.constants.RedisConstant;
import cn.xrfzh.common.enums.VideoQueryType;
import cn.xrfzh.common.redis.RedisServiceClient;
import cn.xrfzh.common.utils.*;
import cn.xrfzh.mail.service.MailUtils;
import cn.xrfzh.tencentcloud.service.SMUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import cn.xrfzh.common.redis.RedisCacheClient;
import cn.xrfzh.common.redis.annotation.ClearRedisCache;
import cn.xrfzh.common.axios.AxiosEnum;
import cn.xrfzh.common.axios.AxiosResult;
import cn.xrfzh.common.exception.MyCustomException;
import cn.xrfzh.biz.user.mapper.UserMapper;
import cn.xrfzh.api.user.pojo.User;
import cn.xrfzh.biz.user.service.IUserService;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 用于存储用户信息
 *
 * @author fzh
 * @email e_341877120_mail@163.com
 * @date 2023-10-24 08:33:54
 */
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    private final RedisCacheClient redisCacheClient;

    private final SMUtils smUtils;

    private final MailUtils mailUtils;

    private final HttpServletRequest request;

    private final RedisServiceClient redisServiceClient;

    private final PasswordEncoder passwordEncoder;

    private final ThreadPoolExecutor threadPoolExecutor;

    private final VideoMapper videoMapper;

    /**
     * 根据条件查询列表
     *
     * @param userDto
     * @return
     */
    @Override
    public AxiosResult selectList(UserDto userDto) {
        LambdaQueryWrapper<User> lqw;
        if (XrObjectUtil.checkOfAllNull(userDto)) {
            lqw = null;
        } else {
            lqw = new LambdaQueryWrapper<>();
        }
        List<User> userList = redisCacheClient.queryAllFromRedisIfExists("user", 1800L,
                TimeUnit.SECONDS, User.class, lqw, this::list);
        boolean flag = ObjectUtils.isNotEmpty(userList);
        return flag ? AxiosResult.selectSuccess(userList) : AxiosResult.selectError();
    }

    /**
     * 根据条件分页查询
     *
     * @param currentPage
     * @param pageSize
     * @param userDto
     * @return
     */
    @Override
    public AxiosResult selectPage(int currentPage, int pageSize, UserDto userDto) {
        LambdaQueryWrapper<User> lqw;
        if (XrObjectUtil.checkOfAllNull(userDto)) {
            lqw = null;
        } else {
            lqw = new LambdaQueryWrapper<>();
        }
        XrPageUtil xrPageUtil = redisCacheClient.queryByPageFromRedisIfExists("user", 1800L, TimeUnit.SECONDS,
                currentPage, pageSize, lqw, this::list);
        boolean flag = ObjectUtils.isNotEmpty(xrPageUtil);
        return flag ? AxiosResult.selectSuccess(xrPageUtil) : AxiosResult.selectError();
    }

    /**
     * 根据id查询单个
     *
     * @param id
     * @return
     */
    @Override
    public AxiosResult selectOneById(Long id) {
        if (ObjectUtils.isNotEmpty(id)) {
            User user = getById(id);
            boolean flag = ObjectUtils.isNotEmpty(user);
            return flag ? AxiosResult.selectSuccess(user) : AxiosResult.selectError();
        }
        throw new MyCustomException(AxiosEnum.PARAMETER_ERROR);
    }

    /**
     * 新增
     *
     * @param userDto
     * @return
     */
    @ClearRedisCache("user")
    @Override
    public AxiosResult insert(UserDto userDto) {
        if (ObjectUtils.isNotEmpty(userDto)) {
            boolean flag = save(userDto);
            return flag ? AxiosResult.insertSuccess() : AxiosResult.insertError();
        }
        throw new MyCustomException(AxiosEnum.PARAMETER_ERROR);
    }

    /**
     * 更新
     *
     * @param userDto
     * @return
     */
    @ClearRedisCache("user")
    @Override
    public AxiosResult update(UserDto userDto) {
        if (ObjectUtils.isNotEmpty(userDto)) {
            boolean flag = updateById(userDto);
            return flag ? AxiosResult.updateSuccess() : AxiosResult.updateError();
        }
        throw new MyCustomException(AxiosEnum.PARAMETER_ERROR);
    }

    /**
     * 根据id删除单个
     *
     * @param id
     * @return
     */
    @ClearRedisCache("user")
    @Override
    public AxiosResult delete(Long id) {
        if (ObjectUtils.isNotEmpty(id)) {
            boolean flag = removeById(id);
            return flag ? AxiosResult.deleteSuccess() : AxiosResult.deleteError();
        }
        throw new MyCustomException(AxiosEnum.PARAMETER_ERROR);
    }

    /**
     * 根据id集合批量删除
     *
     * @param idList
     * @return
     */
    @ClearRedisCache("user")
    @Override
    public AxiosResult deleteBatch(List<Long> idList) {
        if (ObjectUtils.isEmpty(idList) || idList.size() == 0) {
            throw new MyCustomException(AxiosEnum.PARAMETER_ERROR);
        }
        boolean flag = removeByIds(idList);
        return flag ? AxiosResult.deleteSuccess() : AxiosResult.deleteError();
    }

    /**
     * 导入文件
     *
     * @param file
     * @return
     */
    @ClearRedisCache("user")
    @Override
    public AxiosResult importFile(MultipartFile file) {
        boolean flag = false;
        try {
            InputStream inputStream = file.getInputStream();
            ExcelReader er = ExcelUtil.getReader(inputStream);
            List<User> userList = er.readAll(User.class);
            flag = ParallelStreamUtil.insertBatchOfLarge(userList, baseMapper::insertBatch);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return flag ? AxiosResult.success(AxiosEnum.IMPORT_SUCCESS) : AxiosResult.error(AxiosEnum.IMPORT_ERROR);
    }

    /**
     * 导出文件
     *
     * @param response
     */
    @Override
    public void exportFile(HttpServletResponse response) {
        try {
            List<User> userList = list();
            ExcelWriter ew = ExcelUtil.getWriter(true);
            ew.setOnlyAlias(true);
            ew.write(userList);
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
            String fileName = URLEncoder.encode("Excel导出表", "UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
            ServletOutputStream sos = response.getOutputStream();
            ew.flush(sos, true);
            sos.close();
            IoUtil.close(sos);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 用户登录
     *
     * @param loginDto
     * @return
     */
    @Override
    public AxiosResult login(LoginDto loginDto) {
        String username;
        String password;
        try {
            EncryptionService encryptionService = new EncryptionService(AESAlgorithmConstant.SECRET_KEY, AESAlgorithmConstant.IV);
            username = encryptionService.decrypt(loginDto.getUsername());
            password = encryptionService.decrypt(loginDto.getPassword());
        } catch (Exception e) {
            throw new MyCustomException("密文解密失败");
        }
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.eq(User::getPhone, username);
        User userInfo;
        userInfo = getOne(lqw);
        if (ObjectUtils.isEmpty(userInfo)) {
            return AxiosResult.error("用户名不存在");
        }
        // 将数据库中的加密密码进行解密校验
        boolean ifPasswordRight = DigestUtil.bcryptCheck(password, userInfo.getPassword());
        if (!ifPasswordRight) {
            return AxiosResult.error("用户名与密码不匹配");
        }
        StpUtil.login(userInfo.getUserId());
        LoginVo loginVo = new LoginVo();
        BeanUtils.copyProperties(userInfo, loginVo);
        loginVo.setToken(StpUtil.getTokenValue());
        // 密码置空处理
        loginVo.setPassword(null);
        return AxiosResult.success("登录成功", loginVo);
    }

    /**
     * 注销登录
     *
     * @return
     */
    @Override
    public AxiosResult logout() {
        StpUtil.logout();
        return AxiosResult.success("注销成功");
    }

    /**
     * 手机验证码登录
     *
     * @param loginByPhoneCaptchaDto
     * @return
     */
    @Override
    public AxiosResult loginByPhoneCaptcha(LoginByPhoneCaptchaDto loginByPhoneCaptchaDto) {
        String phone = loginByPhoneCaptchaDto.getPhone();
        String captcha = loginByPhoneCaptchaDto.getCaptcha();
        try {
            EncryptionService encryptionService = new EncryptionService(AESAlgorithmConstant.SECRET_KEY, AESAlgorithmConstant.IV);
            phone = encryptionService.decrypt(phone);
            if (phone.length() != PhoneLengthConstant.PHONE_LENGTH) {
                throw new MyCustomException("手机号长度不正确");
            }
            captcha = encryptionService.decrypt(captcha);
        } catch (Exception e) {
            throw new MyCustomException("密文解密失败");
        }
        // 1. 从redis中获得验证码进行校验是否匹配
        return loginByCaptcha(phone, captcha);
    }

    /**
     * 创建新用户
     *
     * @param phoneOrEmail
     * @return
     */
    private User createNewUser(String phoneOrEmail) {
        User user = new User();
        // 生成16位随机用户名
        user.setUsername(RandomNumberGenerator.generateRandomStringWithoutSymbol(16))
                .setPassword(passwordEncoder.encode(DefaultUserInfoConstant.DEFAULT_PASSWORD))
                .setProfilePictureUrl(DefaultUserInfoConstant.DEFAULT_USER_AVATAR);
        if (MailPatternMatchUtil.isMatchedOrNot(phoneOrEmail)) {
            user.setEmail(phoneOrEmail);
        } else {
            user.setPhone(phoneOrEmail);
        }
        // 保存用户基本信息到数据库
        boolean flag = save(user);
        return flag ? user : null;
    }

    /**
     * 查询手机号或邮箱是否存在（即该手机号或邮箱是否注册）
     *
     * @param phoneOrEmail
     * @return
     */
    private User queryPhoneExists(String phoneOrEmail) {
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        if (MailPatternMatchUtil.isMatchedOrNot(phoneOrEmail)) {
            lqw.eq(StringUtils.isNotBlank(phoneOrEmail), User::getEmail, phoneOrEmail);
        } else {
            lqw.eq(StringUtils.isNotBlank(phoneOrEmail), User::getPhone, phoneOrEmail);
        }
        return getOne(lqw);
    }

    /**
     * 邮箱验证码登录
     *
     * @param loginByEmailCaptchaDto
     * @return
     */
    @Override
    public AxiosResult loginByEmailCaptcha(LoginByEmailCaptchaDto loginByEmailCaptchaDto) {
        String email = loginByEmailCaptchaDto.getEmail();
        String captcha = loginByEmailCaptchaDto.getCaptcha();
        return loginByCaptcha(email, captcha);
    }

    /**
     * 验证码方式登录
     *
     * @param phoneOrEmail
     * @param captcha
     * @return
     */
    @NotNull
    private AxiosResult loginByCaptcha(String phoneOrEmail, String captcha) {
        // 1. 从redis中获得验证码进行校验是否匹配
        String redisCode = redisServiceClient.get(RedisConstant.PHONE_OR_EMAIL_KEY_PREFIX + phoneOrEmail);
        if (org.apache.commons.lang3.StringUtils.isBlank(redisCode) || !redisCode.equalsIgnoreCase(captcha)) {
            return AxiosResult.error("验证码输入错误");
        }
        // 2. 查询数据库，判断用户是否存在
        User userInfo = queryPhoneExists(phoneOrEmail);
        if (ObjectUtils.isEmpty(userInfo)) {
            // 2.1 如果用户为空，表示没有注册过，则为null，需要注册用户信息入库
            userInfo = createNewUser(phoneOrEmail);
        }
        // 3. 如果不为空，可以继续下方业务，可以保存用户会话信息
        if (ObjectUtils.isNotEmpty(userInfo)) {
            StpUtil.login(userInfo.getUserId());
            // 4. 用户登录注册成功以后，删除redis中的短信验证码
            redisServiceClient.del(RedisConstant.PHONE_OR_EMAIL_KEY_PREFIX + phoneOrEmail);
            LoginVo loginVo = new LoginVo();
            BeanUtils.copyProperties(userInfo, loginVo);
            loginVo.setToken(StpUtil.getTokenValue());
            // 5. 密码置空处理
            loginVo.setPassword(null);
            return AxiosResult.success("登录成功", loginVo);
        }
        return AxiosResult.success("登录失败");
    }

    /**
     * 发送验证码（手机号或者邮箱）
     *
     * @param phoneOrEmail
     * @return
     */
    @Override
    public AxiosResult sendCaptcha(String phoneOrEmail) {
        try {
            EncryptionService encryptionService = new EncryptionService(AESAlgorithmConstant.SECRET_KEY, AESAlgorithmConstant.IV);
            phoneOrEmail = encryptionService.decrypt(phoneOrEmail);
            if (phoneOrEmail.length() != PhoneLengthConstant.PHONE_LENGTH) {
                throw new MyCustomException("手机号长度不正确");
            }
        } catch (Exception e) {
            throw new MyCustomException("密文解密失败");
        }
        String redisIP = redisServiceClient.get(phoneOrEmail);
        if (null == redisIP) {
            // 获得当前用户IP
            String userIp = IpUtil.getIpAddr(request);
            // 根据用户IP进行限制，限制用户在60秒之内只能获得一次验证码
            redisServiceClient.setnx60s(phoneOrEmail, userIp);
            String code = String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
            log.info(code);
            try {
                // 调用线程池，创建异步线程完成耗时较长的发送邮箱验证码操作
                String finalPhoneOrEmail = phoneOrEmail;
                threadPoolExecutor.execute(() -> {
                    // 参数若符合邮箱的正则表达式，则发送邮箱验证码
                    if (MailPatternMatchUtil.isMatchedOrNot(finalPhoneOrEmail)) {
                        mailUtils.sendMail(finalPhoneOrEmail, "邮箱登录验证码", "【蔚一简公众号】验证码为：" + code + "，您正在进行注册或登录操作，若非本人操作，请勿泄露此验证码。");
                        // 否则发送手机验证码
                    } else {
//                        smUtils.sendSM(finalPhoneOrEmail, code);
                    }
                });
            } catch (Exception e) {
                log.error("发送手机或邮箱验证码异常");
            }
            // 将验证码放入到Redis中，用于后续的验证，有效时间1分钟
            redisServiceClient.set(RedisConstant.PHONE_OR_EMAIL_KEY_PREFIX + phoneOrEmail, code, 60);
            return AxiosResult.success("验证码发送成功", code);
        }
        return AxiosResult.error("一分钟内只允许发送一次验证码，请稍后重试");
    }

    /**
     * 查询个人主页中，我发布/喜欢/收藏的作品
     *
     * @return
     */
    @Override
    public AxiosResult selectWorksByType(Integer queryType) {
        if (queryType != VideoQueryType.PUBLISHED.getValue()
                && queryType != VideoQueryType.LIKED.getValue()
                && queryType != VideoQueryType.FAVORITE.getValue()) {
            throw new MyCustomException("视频查询类型的数值不合法，取值集合为1，2，3");
        }
        // 获取当前登录人的ID
        int userId = StpUtil.getLoginIdAsInt();
        // 发布
        if (queryType == VideoQueryType.PUBLISHED.getValue()) {
            return AxiosResult.selectSuccess(videoMapper.selectByPublished(userId));
            // 喜欢
        } else if (queryType == VideoQueryType.LIKED.getValue()) {
            return AxiosResult.selectSuccess(videoMapper.selectByLiked(userId));
            // 收藏
        } else {
            return AxiosResult.selectSuccess(videoMapper.selectByFavorite(userId));
        }
    }

    /**
     * 忘记密码
     *
     * @param forgetPasswordDto
     * @return
     */
    @Override
    public AxiosResult forgetPassword(ForgetPasswordDto forgetPasswordDto) {
        String phoneOrEmail = forgetPasswordDto.getPhoneOrEmail();
        String newPassword = forgetPasswordDto.getNewPassword();
        String captcha = forgetPasswordDto.getCaptcha();
        try {
            EncryptionService encryptionService = new EncryptionService(AESAlgorithmConstant.SECRET_KEY, AESAlgorithmConstant.IV);
            phoneOrEmail = encryptionService.decrypt(phoneOrEmail);
            // 若是手机号，则校验解密后的手机号长度
            if (!MailPatternMatchUtil.isMatchedOrNot(phoneOrEmail) && phoneOrEmail.length() != PhoneLengthConstant.PHONE_LENGTH) {
                throw new MyCustomException("手机号长度不正确");
                // 若是邮箱，则校验邮箱格式
            } else if (MailPatternMatchUtil.isMatchedOrNot(phoneOrEmail)) {
                throw new MyCustomException("邮箱格式不正确");
            }
            newPassword = encryptionService.decrypt(newPassword);
            captcha = encryptionService.decrypt(captcha);
        } catch (Exception e) {
            throw new MyCustomException("密文解密失败");
        }
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.eq(MailPatternMatchUtil.isMatchedOrNot(phoneOrEmail) ? User::getEmail : User::getPhone, phoneOrEmail);
        User userInfo = getOne(lqw);
        if (ObjectUtils.isEmpty(userInfo)) {
            throw new MyCustomException("手机号或邮箱不存在");
        }
        // 从redis中获得验证码进行校验是否匹配
        String redisCode = redisServiceClient.get(RedisConstant.PHONE_OR_EMAIL_KEY_PREFIX + phoneOrEmail);
        if (org.apache.commons.lang3.StringUtils.isBlank(redisCode) || !redisCode.equalsIgnoreCase(captcha)) {
            return AxiosResult.error("验证码输入错误");
        }
        // 执行设置新密码的逻辑（先将新密码解密然后对其加密并替换旧密码）
        userInfo.setPassword(passwordEncoder.encode(newPassword));
        boolean flag = updateById(userInfo);
        return flag ? AxiosResult.success("新密码设置成功") : AxiosResult.error("新密码设置失败");
    }

}
